/**
 * sffs_t - Shift Flash File System 滑动式 FLASH 文件系统
 * Copyright (c) 2017 深圳市智辰科技有限公司
 * All rights reserved.
 * 张勇 41204@qq.com
 */

#include <string.h>

#include "utils.h"
#include "unios.h"
#include "sffs.h"

#include "log.h"
// 在包含用户日志头文件 "log4app.h" 之前，应先定义 LOGLEVEL 和 LOGTAG
#define LOGLEVEL LOG_DEBUG
#define LOGTAG   "SFFS"
#include "log4app.h"


// 文件在 Flash 中的数据结构
#pragma pack(push, 4)
typedef struct _sffs_file {
	// 文件有效性标志(FLASH 擦除之后为 0xFF，只有当此标志为 SFFS_FILE_TAG 时才表示是一个合法的文件头)
	uint8_t TAG;
	// 文件标志
    sffs_flag_t flag:8;
	// 保留
	uint16_t _res;
	// 文件名
	char name[SFFS_FILENAME_SIZE + 1];
	// 文件时间戳(自 1970-1-1 00:00:00 到创建时的秒数)
	uint32_t timestamp;
	// 文件数据长度
	uint32_t len;
	// 文件数据(实际有效长度为 len)
	uint8_t data[4];
} sffs_file_t;
#pragma pack(pop)

// 文件头开销
#define HEADCOST        (sizeof(sffs_file_t) - 4)
// 文件指针转换为地址
#define FILEADDR(f)     ((uintptr_t)(f))
// 文件结束地址
#define FENDADDR(f)     align(FILEADDR(f) + HEADCOST + f->len, 4)
// 下一个文件
#define NEXTFILE(f)     (sffs_file_t*)FENDADDR(f)

// 检查目标地址是否处于文件系统的存储区
static inline bool _validate_addr(const sffs_t *sffs, uint32_t addr) {
	return addr >= sffs->startAddr && addr < sffs->startAddr + sffs->totalSize;
}

// 计算目标地址所在的页地址
static inline uint32_t _page_addr(const sffs_t *sffs, uint32_t addr) {
	return addr - (addr % sffs->pageSize);
}

// 是否是有效的文件
static bool _validate_file(const sffs_t *sffs, sffs_file_t *file) {
	if(!sffs || !file) return false;
	return	FILEADDR(file) >= sffs->startAddr &&								// 确保文件头位于文件系统起始地址之后
			file->TAG == SFFS_FILE_TAG &&										// 确保文件有效
			file->flag < SFFS_FLAG_MAX &&										// 确保文件标志合法
			FENDADDR(file) <= (sffs->startAddr + sffs->totalSize) &&            // 确保文件长度合法
			strlen(file->name) > 0 && strlen(file->name) <= SFFS_FILENAME_SIZE;	// 确保文件名长度合法
}

// 找到文件系统存储区最后面的空闲区地址
static uintptr_t _locate_freespace(const sffs_t *sffs) {
    sffs_file_t* file = (sffs_file_t*) sffs->startAddr;
	while(_validate_file(sffs, file)) { file = NEXTFILE(file); }
	return (uintptr_t)file;
}

uint32_t sffs_get_free_size(const sffs_t *sffs) {
	if(!sffs) { return 0; }
    uint32_t flashEnd = sffs->startAddr + sffs->totalSize;
    uint32_t freeAddr = _locate_freespace(sffs) + HEADCOST;
	return (freeAddr >= flashEnd) ? 0 : flashEnd - freeAddr;
}

sffs_file_t* sffs_next_file(const sffs_t *sffs, sffs_file_t *file) {
	if(!sffs) return NULL;
    file = file ? NEXTFILE(file) : (sffs_file_t*)sffs->startAddr; // 如果 prev = NULL 则从头开始遍历
	return _validate_file(sffs, file) ? file : NULL;
}

uint32_t sffs_get_file_count(const sffs_t *sffs) {
	uint32_t count = 0;
    sffs_file_t* file = NULL;
	while((file = sffs_next_file(sffs, file)) != NULL) count++;
	return count;
}

sffs_file_t* sffs_find_file(const sffs_t *sffs, const char *name) {
	if(!sffs || !name) { return NULL; }
    sffs_file_t* file = NULL;
	while((file = sffs_next_file(sffs, file)) != NULL) { if(strcmp(name, file->name) == 0) return file; }
	return NULL;
}

/// 页缓存写入 FLASH
/// @return 返回写入的数据的有效长度
static uint32_t _write(const sffs_t *sffs, uint32_t headAddr, uint32_t len, void *payload) {
	if(!sffs || !sffs->dcache || !len || !payload || !_validate_addr(sffs, headAddr) || !_validate_addr(sffs, headAddr + len - 1)) { return 0; }

	// !!! 注意：写 Flash 过程中，禁止中断 !!!
	unios_critical_section_t cs;
	unios_critical_section_init(&cs);
	unios_critical_section_enter(cs, 1000); // {{{
	
	// 尾部地址
	uint32_t tailAddr = headAddr + len;
	// 缓存数据游标
	uint8_t *pc = sffs->dcache;
	// 净荷数据游标
	uint8_t *pp = payload;
	
	// 数据头所在的目标页地址
	uint32_t headPageAddr = _page_addr(sffs, headAddr);
	// 数据头所在的目标页中有多少 dummy 数据
	uint32_t headDummy = headAddr - headPageAddr;
	// 数据头所在的目标页中能存放多少数据
	uint32_t headPayload = sffs->pageSize - headDummy;
	
	// 数据尾所在的目标页地址
	uint32_t tailPageAddr = _page_addr(sffs, tailAddr);
	// 数据尾所在的目标页中能存放多少数据
	uint32_t tailPayload = tailAddr - tailPageAddr;
	// 数据尾所在的目标页中有多少 dummy 数据
	uint32_t tailDummy = sffs->pageSize - tailPayload;
	
	if(headPageAddr == tailPageAddr) {
		// 头尾在同一页中，也就是说一页就可以写完
		// 读入头部的 dummy 数据到页缓存
		memcpy(pc, (void*)headPageAddr, headDummy); pc += headDummy;
		// 净荷写入页缓存
		memcpy(pc, pp, len); pc += len; pp += len;
		// 读入尾部的 dummy 数据到页缓存
		memcpy(pc, (void*)tailAddr, tailDummy); pc += tailDummy;
		// 擦除
		sffs->erase(headPageAddr - sffs->flashBaseAddr, sffs->pageSize);
		// 写入缓存
		sffs->write(headPageAddr - sffs->flashBaseAddr, sffs->pageSize, sffs->dcache); pc = sffs->dcache;
	} else {
		// 写入头部页
		// 读入头部的 dummy 数据到页缓存
		memcpy(pc, (void*)headPageAddr, headDummy); pc += headDummy;
		// 净荷头部写入页缓存
		memcpy(pc, pp, headPayload); pc += headPayload; pp += headPayload;
		// 擦除
		sffs->erase(headPageAddr - sffs->flashBaseAddr, sffs->pageSize);
		// 写入缓存
		sffs->write(headPageAddr - sffs->flashBaseAddr, sffs->pageSize, sffs->dcache); pc = sffs->dcache;
		
		// 中间页首地址
		uint32_t bodyPageAddr = headPageAddr + sffs->pageSize;
		// 中间页数据总长度
		uint32_t bodySize = tailPageAddr - bodyPageAddr;
		// 判断是否有中间页
		if(bodySize) {
			// 判断净荷是否也是在 SFFS 存储区中
			if(_validate_addr(sffs, (uint32_t)pp)) {
				// 净荷在 SFFS 中，必须一次写一页，否则可能有问题
				for(; bodyPageAddr < tailPageAddr; bodyPageAddr += sffs->pageSize) {
					// 净荷写入页缓存
					memcpy(sffs->dcache, pp, sffs->pageSize); pp += sffs->pageSize;
					// 擦除
					sffs->erase(bodyPageAddr - sffs->flashBaseAddr, sffs->pageSize);
					// 写入缓存
					sffs->write(bodyPageAddr - sffs->flashBaseAddr, sffs->pageSize, sffs->dcache);
				}
			} else {
				// 净荷不是在 SFFS 中，可以一次性写入中间页
				// 整体擦除
				sffs->erase(bodyPageAddr - sffs->flashBaseAddr, bodySize);
				// 整体写入
				sffs->write(bodyPageAddr - sffs->flashBaseAddr, bodySize, pp); pp += bodySize;
			}
		}
		
		// 写入尾部页
		// 净荷尾部写入页缓存
		memcpy(pc, pp, tailPayload); pc += tailPayload; pp += tailPayload;
		// 读入尾部的 dummy 数据到页缓存
		memcpy(pc, (void*)tailAddr, tailDummy); pc += tailDummy;
		// 擦除
		sffs->erase(tailPageAddr - sffs->flashBaseAddr, sffs->pageSize);
		// 写入缓存
		sffs->write(tailPageAddr - sffs->flashBaseAddr, sffs->pageSize, sffs->dcache); pc = sffs->dcache;
	}
	
	// 恢复中断状态
	unios_critical_section_destroy(&cs); // }}}
	
	// 返回写入的数据的有效长度
	return pp - (uint8_t*)payload;
}

std_err_t sffs_delete_file(const sffs_t *sffs, sffs_file_t *file) {
	if(!sffs || !file) { return STD_ERR_INVALID_ARG; }
	if(!_validate_file(sffs, file)) { return STD_ERR_NOT_FOUND; }
	if(file->flag & SFFS_FLAG_SYSTEM) { return STD_ERR_PROHIBITED; }
    logw("!!! Delete '%s' ...\r\n", file->name);
	
	// 看此文件后面是否还有其它文件
    sffs_file_t *next = sffs_next_file(sffs, file);
	if(!next) {
		// 如果后面没有文件了，则直接删除此文件(清空文件头)就可以了
		uint32_t empty = 0;
		_write(sffs, (uint32_t)file, 4, &empty);
	} else {
		// 后面还有文件，需要把后面的所有文件向前移到当前文件的位置(注意清除最后一个文件后面的文件头空间，以免误认为是一个文件)
		uint32_t size = _locate_freespace(sffs) + HEADCOST - FILEADDR(next);
		_write(sffs, (uint32_t)file, size, next);
	}
    logi("After deletion, %d bytes free.\r\n", sffs_get_free_size(sffs));
	return STD_ERR_OK;
}

std_err_t sffs_truncate(const sffs_t *sffs, uint32_t idx) {
	if(!sffs) { return STD_ERR_INVALID_ARG; }
	uint32_t count = 0;
    sffs_file_t* file = NULL;
	while((file = sffs_next_file(sffs, file)) != NULL) {
		if(idx == count) {
            // 清空当前文件头就可以把后面的文件全部清除
            uint32_t empty = 0;
            _write(sffs, (uint32_t)file, 4, &empty);
            return STD_ERR_OK;
        }
        count++;
	}
	
	return STD_ERR_NOT_FOUND;
}

sffs_file_hcache_t* sffs_create_file(sffs_t *sffs, const char *name) {
	if(!sffs || !name || strlen(name) < 1 || strlen(name) > SFFS_FILENAME_SIZE) return NULL;
    if(!sffs_get_free_size(sffs)) return NULL; // Flash 中没有空间创建新文件了
    
    // 当前还有其它正在 append 操作的文件在使用缓冲区，不允许再次创建文件
    sffs_file_hcache_t* fhead = &sffs->hcache;
    if(fhead->TAG == SFFS_FILE_TAG) return NULL;
    
    // 填写在缓冲区中的文件头信息
	fhead->TAG = SFFS_FILE_TAG;
	fhead->flag = SFFS_FLAG_NONE;
	strcpy(fhead->name, name);
	fhead->_res = 0;
	fhead->timestamp = unios_rtcsec_get();
	fhead->len = 0;

    // 头信息复制到页缓冲区
    sffs->dclen = HEADCOST;
    memcpy(sffs->dcache, fhead, HEADCOST);
    return fhead;
}
	
// 缓存中有多少剩余空间
#define cfree() (sffs->pageSize - sffs->dclen)

uint32_t sffs_file_append(sffs_t *sffs, sffs_file_hcache_t *hcache, void *payload, uint32_t len, bool final) {
	if(!sffs || !hcache || !payload || !len) { return 0; }
	uint32_t size;
	
	// 净荷数据游标
	uint8_t *pp = payload;
	// 还剩下多少净荷数据
	#define plen() (len - (pp - (uint8_t*)payload))
	
	// 文件末尾地址
	uint32_t ftail = (uintptr_t)hcache->pdata + hcache->len;
	// 文件末尾所在的页地址
	uint32_t ftailPageAddr = _page_addr(sffs, ftail);
	
	if(!sffs->dclen) {
		// 拷贝文件末尾到页头部之间的 dummy 数据
		size = ftail - ftailPageAddr;
		memcpy(sffs->dcache, (void*)ftailPageAddr, size); sffs->dclen += size;
	}

	while(plen() > 0) {
		// 填写缓存
		size = min(plen(), cfree());
		memcpy(sffs->dcache + sffs->dclen, pp, size); sffs->dclen += size; pp += size; hcache->len += size;
		
		// 如果缓存满了，或 final 了，则写入缓存
		if(!cfree() || final) {
			// 擦除
			sffs->erase(ftailPageAddr - sffs->flashBaseAddr, sffs->pageSize);
			// 写入缓存
			sffs->write(ftailPageAddr - sffs->flashBaseAddr, sffs->pageSize, sffs->dcache); sffs->dclen = 0;
			logd("Append file '%s': %d bytes\r\n", hcache->name, size);
			// 更新地址信息
			ftail = (uint32_t)(hcache->pdata + hcache->len);
			ftailPageAddr = _page_addr(sffs, ftail);
		}
	}

	if(final) {
		// 写入文件头
		_write(sffs, (uint32_t)hcache->pdata - HEADCOST, HEADCOST, hcache);
		logd("File '%s' final: %d bytes\r\n", hcache->name, hcache->len);
	}
	
	return len;
}

uint32_t sffs_file_modify(sffs_t *sffs, sffs_file_t* file, uint32_t offset, void *payload, uint32_t len) {
	if(!sffs || !file || !payload || !len || file->flag & SFFS_FLAG_READONLY || offset + len > file->len) { return 0; }
	uint32_t size;
		
	// 净荷数据游标
	uint8_t *pp = payload;
	// 还剩下多少净荷数据
	#define plen() (len - (pp - (uint8_t*)payload))
	
	// offset 地址
	uint32_t offsetAddr = (uint32_t)(file->data + offset);
	// offset 所在页地址
	uint32_t offsetPageAddr = _page_addr(sffs, offsetAddr);

	while(plen() > 0) {
		// 拷贝 offset 所在页的所有 dummy 数据
		memcpy(sffs->dcache, (void*)offsetPageAddr, sffs->pageSize); sffs->dclen = offsetAddr - offsetPageAddr;
		// 改写缓存
		size = min(plen(), cfree());
		memcpy(sffs->dcache + sffs->dclen, pp, size); sffs->dclen += size; pp += size; offsetAddr += size;
		
		// 如果缓存满了，或数据改完了，则写入缓存
		if(!cfree() || !plen()) {
			// 擦除
			sffs->erase(offsetPageAddr - sffs->flashBaseAddr, sffs->pageSize);
			// 写入缓存
			sffs->write(offsetPageAddr - sffs->flashBaseAddr, sffs->pageSize, sffs->dcache); sffs->dclen = 0;
			//logd("Flush file '%s': %d bytes\r\n", file->head.name, size);
			// 更新地址信息
			offsetPageAddr = _page_addr(sffs, offsetAddr);
		}
	}
	
	return len;
}

void sffs_dump_file(const sffs_t *sffs, sffs_file_t *file) {
	if(!sffs || !file) { return; }
	logd("%d# 0x%08X, 0x%02X, '%s', %d bytes, '%s'\r\n", file->_res, (uint32_t)file, file->flag, file->name, file->len, unios_rtc_local_string(file->timestamp));
}

void sffs_dump_all(const sffs_t *sffs) {
	if(!sffs) { return; }
	uint32_t count = 0;
    uint32_t size = 0;
    sffs_file_t* file = NULL;
	while((file = sffs_next_file(sffs, file)) != NULL) {
        size += file->len;
		logd("%d# 0x%08X, 0x%02X, '%s', %d bytes, '%s'\r\n", file->_res, (uint32_t)file, file->flag, file->name, file->len, unios_rtc_local_string(file->timestamp));
		count++;
	}
    logd("Total %d files, %d bytes\r\nFree space: %d bytes\r\n", count, size, sffs_get_free_size(sffs));
}
